package dao

import (
	"database/sql"
	"fmt"
	isql "game/game-db/mysql/sql"
	"game/game-db/orm"
	"game/game-db/orm/meta"
	"reflect"
	"time"

	"go.uber.org/zap"
)

type OriginDao[T orm.IDbEntity] struct {
	database   *sql.DB
	entityMeta meta.IEntityMeta
	slowTime   int32
}

func (t *OriginDao[T]) EntityMeta() meta.IEntityMeta {
	return t.entityMeta
}

func (t *OriginDao[T]) Sql() isql.ISql {
	return nil
}

func insertWireAutoId[T orm.IDbEntity](entityMeta meta.IEntityMeta, database *sql.DB, entity T, sql string) error {
	fmt.Println(sql)
	stmt, err := database.Prepare(sql)
	if err != nil {
		fmt.Println(err)
		return err
	}
	defer stmt.Close()

	now := time.Now()
	entity.SetInsertTime(now)
	entity.SetUpdateTime(now)
	rValue := reflect.ValueOf(entity)
	args := entityMeta.TakeInsertValue(rValue.Elem())
	// fmt.Println("insertWithAutoId args", args)
	res, err := stmt.Exec(args...)
	if err != nil {
		return err
	}
	key, err := res.LastInsertId()
	if err != nil {
		return err
	}
	// fmt.Println("insertWithAutoId key", key)
	entityMeta.FillKeyValue(rValue.Elem(), key)
	return nil
}

func insert[T orm.IDbEntity](entityMeta meta.IEntityMeta, database *sql.DB, entity T, sql string) error {
	fmt.Println(sql)
	stmt, err := database.Prepare(sql)
	if err != nil {
		return err
	}
	defer stmt.Close()

	now := time.Now()
	entity.SetInsertTime(now)
	entity.SetUpdateTime(now)
	rValue := reflect.ValueOf(entity)
	args := entityMeta.TakeInsertValue(rValue.Elem())
	// fmt.Println("insertWithIdentityId args", args)
	_, err = stmt.Exec(args...)
	return err
}

func insertAllWireAutoId[T orm.IDbEntity](entityMeta meta.IEntityMeta, database *sql.DB, entities []T, sql string) {
	// fmt.Println("insertAllWireAutoId", sql)
	stmt, err := database.Prepare(sql)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	now := time.Now()

	for _, v := range entities {
		v.SetInsertTime(now)
		v.SetUpdateTime(now)
		entity := reflect.ValueOf(v)
		args := entityMeta.TakeInsertValue(entity.Elem())
		res, err := stmt.Exec(args...)
		if err != nil {
			fmt.Println(err)
			continue
		}
		key, err := res.LastInsertId()
		if err != nil {
			fmt.Println(err)
			continue
		}
		entityMeta.FillKeyValue(entity.Elem(), key)
	}
}

func insertAll[T orm.IDbEntity](entityMeta meta.IEntityMeta, database *sql.DB, entities []T, sqlHead string, sqlBody string) error {
	sql := sqlHead
	for i := range entities {
		sql += sqlBody
		if i < len(entities)-1 {
			sql += ","
		}
	}
	// fmt.Println("insertAll", sql)
	stmt, err := database.Prepare(sql)
	if err != nil {
		return err
	}
	defer stmt.Close()

	now := time.Now()

	var batchArgs []interface{}
	for _, entity := range entities {
		entity.SetInsertTime(now)
		entity.SetUpdateTime(now)
		rValue := reflect.ValueOf(entity)
		args := entityMeta.TakeInsertValue(rValue.Elem())
		if batchArgs == nil {
			batchArgs = make([]interface{}, 0, len(args)*len(entities))
		}
		batchArgs = append(batchArgs, args...)
	}
	_, err = stmt.Exec(batchArgs...)
	return err
}

func update[T orm.IDbEntity](entityMeta meta.IEntityMeta, database *sql.DB, entity T, sql string) {
	// fmt.Println(sql)
	stmt, err := database.Prepare(sql)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	entity.SetUpdateTime(time.Now())
	rValue := reflect.ValueOf(entity)
	args := entityMeta.TakeUpdateValue(rValue.Elem())
	// fmt.Println("update args", args)
	_, err = stmt.Exec(args...)
	if err != nil {
		fmt.Println(err)
		return
	}
}

func updateAll[T orm.IDbEntity](entityMeta meta.IEntityMeta, database *sql.DB, entities []T, sql string) {
	tx, err := database.Begin()
	if err != nil {
		fmt.Println(err)
		return
	}
	stmt, err := tx.Prepare(sql)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	for _, entity := range entities {
		entity.SetUpdateTime(time.Now())
		rValue := reflect.ValueOf(entity)
		args := entityMeta.TakeUpdateValue(rValue.Elem())
		_, err := stmt.Exec(args...)
		if err != nil {
			fmt.Println(err, args)
		}
	}
	tx.Commit()
}

func delete[T orm.IDbEntity](entityMeta meta.IEntityMeta, database *sql.DB, entity T, sql string) {
	stmt, err := database.Prepare(sql)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	rValue := reflect.ValueOf(entity)
	args := entityMeta.GetPrimaryKeyValue(rValue.Elem())
	// fmt.Println("delete sql ", sql, " args ", args)
	_, err = stmt.Exec(args)
	if err != nil {
		fmt.Println(err)
		return
	}
}

func deleteAll[T orm.IDbEntity](entityMeta meta.IEntityMeta, database *sql.DB, entities []T, sql string) {
	tx, err := database.Begin()
	if err != nil {
		fmt.Println(err)
		return
	}
	stmt, err := tx.Prepare(sql)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	for _, entity := range entities {
		rValue := reflect.ValueOf(entity)
		args := entityMeta.GetPrimaryKeyValue(rValue.Elem())
		// fmt.Println("delete sql ", sql, " args ", args)
		_, err = stmt.Exec(args)
		if err != nil {
			fmt.Println(err, args)
		}
	}
	tx.Commit()
}

func (t *OriginDao[T]) query(sql string, args ...interface{}) (T, bool) {
	objects := t.queryList(sql, args...)
	if len(objects) > 1 {
		panic("selection result more than one")
	} else if len(objects) == 1 {
		return objects[0], true
	} else {
		var entity T
		return entity, false
	}
}

func (t *OriginDao[T]) queryList(sql string, args ...interface{}) []T {
	rows, err := t.database.Query(sql, args...)
	if err != nil {
		zap.L().Error("Query error", zap.String("sql", sql), zap.Any("args", args))
		return nil
	}
	defer rows.Close()

	columnTypes, err := rows.ColumnTypes()
	if err != nil {
		fmt.Println(err)
		return nil
	}
	result := make([]T, 0)
	for rows.Next() {
		dest := formValueSlice(columnTypes)
		err = rows.Scan(dest...)
		if err != nil {
			fmt.Println("err:", err, dest)
			continue
		}
		entity := t.entityMeta.NewEntityPtr()
		t.entityMeta.FillValue(entity.Elem(), columnTypes, dest)
		result = append(result, entity.Interface().(T))
	}
	return result
}

func Query(entityMeta meta.IEntityBaseMeta, database *sql.DB, sql string, args ...interface{}) interface{} {
	objects := QueryList(entityMeta, database, sql, args...)
	if len(objects) > 1 {
		panic("selection result more than one")
	} else if len(objects) == 1 {
		return objects[0]
	} else {
		return nil
	}
}

func QueryList(entityMeta meta.IEntityBaseMeta, database *sql.DB, sql string, args ...interface{}) []interface{} {
	rows, err := database.Query(sql, args...)
	if err != nil {
		zap.L().Error("Query error", zap.NamedError("error", err), zap.String("sql", sql), zap.Any("args", args))
		return nil
	}
	defer rows.Close()

	columnTypes, err := rows.ColumnTypes()
	if err != nil {
		fmt.Println(err)
		return nil
	}
	result := make([]interface{}, 0)
	for rows.Next() {
		dest := formValueSlice(columnTypes)
		err = rows.Scan(dest...)
		if err != nil {
			fmt.Println("err:", err, dest)
			continue
		}
		entity := entityMeta.NewEntityPtr()
		entityMeta.FillValue(entity.Elem(), columnTypes, dest)
		result = append(result, entity.Interface())
	}
	return result
}

func formValueSlice(columnTypes []*sql.ColumnType) []interface{} {
	valueSlice := make([]interface{}, 0)
	for i := 0; i < len(columnTypes); i++ {
		if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(sql.NullByte{})) {
			var value *byte
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(sql.NullInt16{})) {
			var value *int16
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(sql.NullInt32{})) {
			var value *int32
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(sql.NullInt64{})) {
			var value *int64
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(sql.NullBool{})) {
			var value *bool
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(sql.NullFloat64{})) {
			var value *float64
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(sql.NullString{})) {
			var value *string
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(sql.NullTime{})) {
			var value *string
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(sql.RawBytes{})) {
			var value *string
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(int8(0))) {
			var value *int8
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(int16(0))) {
			var value *int16
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(int32(0))) {
			var value *int32
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(int64(0))) {
			var value *int64
			valueSlice = append(valueSlice, &value)
		} else if columnTypes[i].ScanType().AssignableTo(reflect.TypeOf(string(""))) {
			var value *string
			valueSlice = append(valueSlice, &value)
		} else {
			fmt.Println("unsupport database column type ", columnTypes[i].Name(), columnTypes[i].ScanType().Name())
		}
	}
	return valueSlice
}

func Exec(database *sql.DB, sql string, args ...interface{}) {
	stmt, err := database.Prepare(sql)
	if err != nil {
		fmt.Println("执行语句:", sql, "发生错误:", err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec(args...)
	if err != nil {
		fmt.Println("执行语句:", sql, "发生错误:", err)
	}
}
